package parser;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
import java.io.LineNumberReader;
import java.util.StringTokenizer;

public class DataParser {
	private double [][] parsed;
	EffortEstimator ee;

	/* The numeric values of the effort multipliers */
	static double[][] data = {	
		{0.75, 0.88, 1.00, 1.15, 1.40, 0},	//Required software reliability
		{0   , 0.94, 1.00, 1.08, 1.16, 0},	//Data base size
		{0.70, 0.85, 1.00, 1.15, 1.30, 1.65},	//Process complexity
		{0   , 0   , 1.00, 1.11, 1.30, 1.66},	//Time constraint for CPU
		{0   , 0   , 1.00, 1.06, 1.21, 1.56},	//Main memory constraint
		{0   , 0.87, 1.00, 1.15, 1.30, 0},	//Machine volatility
		{0   , 0.87, 1.00, 1.07, 1.15, 0},	//Turnaround time 
		{1.46, 1.19, 1.00, 0.86, 0.71, 0},	//Analysts capability
		{1.29, 1.13, 1.00, 0.91, 0.82, 0},	//Application experience
		{1.42, 1.17, 1.00, 0.86, 0.70, 0},	//Programmers capability
		{1.21, 1.10, 1.00, 0.90, 0   , 0},	//Virtual machine experience
		{1.14, 1.07, 1.00, 0.95, 0   , 0},	//Language experience
		{1.24, 1.10, 1.00, 0.91, 0.82, 0},	//Modern programming practices
		{1.24, 1.10, 1.00, 0.91, 0.83, 0},	//Use of software tools
		{1.23, 1.08, 1.00, 1.04, 1.10, 0},	//Schedule constraint
		{1.00, 1.00, 1.00, 1.00, 1.00, 1.00},
		{1.00, 1.00, 1.00, 1.00, 1.00, 1.00},
		{1.00, 1.00, 1.00, 1.00, 1.00, 1.00},
	};

	private File f;
	private static int NBR_OF_MULT; // data.length + 2, for the last two fields LOC and ACT_EFFORT.

	public DataParser(String pathname){
		f = new File(pathname);

		BufferedReader in = null;

		try {
			in = new BufferedReader(new FileReader(f));
		} catch (FileNotFoundException e) {
			System.out.println("Error: Could not read file: " + pathname);
			System.exit(1);
		}

		try {
			NBR_OF_MULT = in.readLine().split(",").length;
		} catch (IOException e) {
			System.out.println("Error: Could not read from line in file " + pathname);
			System.exit(1);
		}

		System.out.println("Number of multipliers " + NBR_OF_MULT);


		f = new File(pathname);

		try {
			this.parsed = initParse();
		} catch (FileNotFoundException e) {
			System.out.println("Could not open file.");
		}
	}

	/**
	 * Returns the effort estimated for a project
	 * as a single double value.
	 * 
	 * @param project	The string rep of the project
	 * @param threshold	The threshold
	 * @param kloc		Kilo-line-of-code
	 * @return
	 */
	public double estEffort(String project, double threshold, double kloc){
		ee = new EffortEstimator(kloc); //Kilo Lines of Code

		String[] s = project.split(",");	

		double[] pNew 		= new double[NBR_OF_MULT-1];
		double[] pExisting 	= new double[NBR_OF_MULT-1];
		double[] min 		= getMin();
		double[] max		= getMax();

		int nbrOfLines = getNbrOfLines();

		for(int i = 0; i < nbrOfLines; i++) {
			for(int j = 0; j < NBR_OF_MULT-2; j++) {
				int a = mapLevelToColumn(s[j]);
				pNew[j] 	= data[j][a];
				pExisting[j]	= parsed[i][j];
			}
			
			pNew[NBR_OF_MULT-2]=kloc;
			pExisting[NBR_OF_MULT-2]=parsed[i][NBR_OF_MULT-2];
			
			double similarity = calculateSimilarity(pNew, pExisting, min, max);

			if(similarity >= threshold) {
				Case c = new Case(similarity, parsed[i][NBR_OF_MULT-2], parsed[i][NBR_OF_MULT-1]);
				ee.addCase(c);
			}
		}

		double result = ee.calculate();

		return result;
	}


	public double COCOMOestimation(String project, double kloc) {
		String[] s = project.split(",");
		double [] p = new double[15];
		for(int i = 0; i < 15; i++) {
			p[i] = data[i][mapLevelToColumn(s[i])]; 
		}
		double effortMultipliers = 1;
		for(int i = 0; i < 15; i++) {
			effortMultipliers *= p[i];
		}

		return 3.6 * Math.pow(kloc, 1.1) * effortMultipliers;
	}
	/**
	 * Returns a double matrix-representation
	 * of the projects in the file.
	 * 
	 * @return		The projects in the file represented as a double matrix
	 * @throws FileNotFoundException
	 */
	public double[][] parse() {
		return parsed;
	}

	/**
	 * Reads data from a file, and returns 
	 * its numerical representation.
	 * 
	 * @return		The projects in the file represented as a double matrix
	 * @throws FileNotFoundException
	 */
	private double[][] initParse() throws FileNotFoundException{
		int nbrLines = getNbrOfLines();
		BufferedReader in = new BufferedReader(new FileReader(f));

		double[][] result = new double[nbrLines][NBR_OF_MULT];
		for(int i = 0; i < nbrLines; i++){
			String line = null;

			try {
				line = in.readLine();
			} catch(IOException fe) {
				System.out.println("Could not read from file" + f.getAbsolutePath());
			}

			double[] values = parseProject(line); 

			result[i] = values;

		}
		parsed = result;
		return result;
	}

	/**
	 * Takes a string representation of a project
	 * and returns corresponding double array.
	 * 
	 * @param 		The string representation of a project
	 * @return		The project as a double array
	 */
	public double[] parseProject(String line){
		StringTokenizer st = new StringTokenizer(line, ",");

		String[] tokens = new String[NBR_OF_MULT];

		int size = NBR_OF_MULT;

		for(int i = 0; i < size; i++){
			tokens[i] = st.nextToken();
		}

		return translateRow(tokens);
	}

	/**
	 * Translates a project, represented by a row in the file, into numeric values.
	 * 
	 * @param tokens	A tokenized list of levels in a project
	 * @return 		The row represented in numeric values
	 */
	private double[] translateRow(String[] tokens) {
		double[] values = new double[NBR_OF_MULT];

		for(int i = 0; i < NBR_OF_MULT - 2; i++){
			int p = mapLevelToColumn(tokens[i]);

			values[i] = data[i][p];
		}

		// Add the values for LOC and ACT_EFFORT
		values[NBR_OF_MULT - 1] = Double.parseDouble(tokens[NBR_OF_MULT - 1]);
		values[NBR_OF_MULT - 2] = Double.parseDouble(tokens[NBR_OF_MULT - 2]);

		return values;
	}

	/**
	 * Map a string to an index number.
	 * 
	 * @param 	The string that describes a level
	 * @return	A numeric value 1..5 of the level
	 */
	public static int mapLevelToColumn(String s){
		if (s.equals("Very_Low")){
			return 0;
		} else if (s.equals("Low")){
			return 1;
		} else if (s.equals("Nominal")){
			return 2;
		} else if (s.equals("High")){
			return 3;
		} else if (s.equals("Very_High")){
			return 4;
		} else if (s.equals("Extra_High")){
			return 5;
		} else {
			return -1;
		}
	}

	/**
	 * Returns an array, with the length of a row in the file.
	 * On each place it contains the maximum value of that multiplier.
	 * 
	 * @return	Returns the max double array
	 */
	public double[] getMax(){
		double[] max = new double[NBR_OF_MULT];

		for(int i = 0; i < max.length; i++){
			max[i] = Double.MIN_VALUE;
			for(int j = 0; j < parsed.length; j++){		
				if(parsed[j][i] > max[i]){
					max[i] = parsed[j][i];
				}
			}
		}

		return max;
	}

	/**
	 * Returns an array, with the length of a row in the file.
	 * On each place it contains the minimum value of that multiplier.
	 * 
	 * @return	Returns the min double array
	 */
	public double[] getMin(){
		double[] min = new double[NBR_OF_MULT];

		for(int i = 0; i < min.length; i++){
			min[i] = Double.MAX_VALUE;
			for(int j = 0; j < parsed.length; j++){			
				if(parsed[j][i] < min[i]){
					min[i] = parsed[j][i];
				}
			}
		}

		return min;
	}

	/**
	 * Counts the number of lines in the file f
	 * and returns the result.
	 * 
	 * @return	Number of lines in file, as int
	 */
	public int getNbrOfLines() {
		LineNumberReader lnr = null;

		try {
			lnr = new LineNumberReader(new FileReader(f));
		} catch (FileNotFoundException e) {
			System.out.println("Error: " + f.getAbsolutePath() + " could not be read.");
			System.exit(1);
		}

		try {
			lnr.skip(Long.MAX_VALUE);
		} catch (IOException e) {
			System.out.println("Error: LineReader failed.");
		}

		return lnr.getLineNumber() + 1;
	}

	/**
	 * Calculate the similarity between a generated double array and a data set entry. 
	 * 
	 * @param pNew		The double vector you generate from the GUI
	 * @param pExisting	An existing double vector from a given data set
	 * @param min		The minimum value for each column from the data set
	 * @param max		The maximum value for each column from the data set 
	 * @return 			Returns a value between 0.0 and 1.0 		
	 */
	public static double calculateSimilarity(double[] pNew, double[] pExisting, double[] min, double[] max){
		double delta = 0;

		for (int i = 0; i<pNew.length; i++){
			delta += Math.pow(Math.abs(pNew[i] - pExisting[i])/(max[i]-min[i]),2);
		}
		double distance = Math.sqrt(delta/pNew.length);

		return 1-distance;
	}

	
	/**
	 * Returns the number of projects that estimated effort set is calculated on
	 * 
	 * @return		An integer between 0 and data size. 
	 */
	public int getEstimatedEffortSetSize(){
		return ee.size();
	}

	/**
	 * Returns the number of multipliers.
	 * 
	 * @return
	 */
	public int getNbrOfCols() {
		return NBR_OF_MULT;
	}
}